#include "ModuleSIFT.h"

////////////////////////////////////////////////////////////////////////////////
void ModuleSIFT::init(QCVModuleHandlerInterface * parent)
{
  this->parent=parent;
  
  win=new QWidget();
  layout=new QVBoxLayout(win);
  QHBoxLayout * hlayout=new QHBoxLayout();
  QGridLayout * glayout1=new QGridLayout();
  QGridLayout * glayout2=new QGridLayout();
  QGridLayout * glayout3=new QGridLayout();
  
  use_mask=new QCheckBox(tr("Utilise un masque"));
  use_mask->setChecked(false);
  
  mode=new QComboBox(win);
  mode->addItem("Detecteur (image -> keys)");
  mode->addItem("Descripteur (image+keys -> descripteurs)");  
  mode->addItem("Detecteur + Descripteur (image -> descripteurs)");
  
  sb_nOctaves=new QSpinBox(win);
  sb_nOctaves->setPrefix(tr("nOctaves="));
  sb_nOctaves->setValue(cv::SIFT::CommonParams::DEFAULT_NOCTAVES);
  sb_nOctaveLayers=new QSpinBox(win);
  sb_nOctaveLayers->setPrefix(tr("nOctaveLayers="));
  sb_nOctaveLayers->setValue(cv::SIFT::CommonParams::DEFAULT_NOCTAVE_LAYERS);
  sb_firstOctave=new QSpinBox(win);
  sb_firstOctave->setPrefix(tr("firstOctave="));
  sb_firstOctave->setValue(cv::SIFT::CommonParams::DEFAULT_FIRST_OCTAVE);
  sb_angleMode=new QSpinBox(win);
  sb_angleMode->setPrefix(tr("angleMode="));
  sb_angleMode->setValue(cv::SIFT::CommonParams::FIRST_ANGLE);
  
  sb_edgeThreshold=new QDoubleSpinBox(win);
  sb_edgeThreshold->setPrefix(tr("edgeThreshold="));
  sb_edgeThreshold->setValue(cv::SIFT::DetectorParams::GET_DEFAULT_EDGE_THRESHOLD());
  sb_edgeThreshold->setRange(-100,100);
  sb_threshold=new QDoubleSpinBox(win);
  sb_threshold->setPrefix(tr("threshold="));
  sb_threshold->setValue(cv::SIFT::DetectorParams::GET_DEFAULT_THRESHOLD());
  sb_threshold->setRange(-100,100);
  
  magnification=new QDoubleSpinBox(win);
  magnification->setPrefix(tr("magnification="));
  magnification->setValue(cv::SIFT::DescriptorParams::GET_DEFAULT_MAGNIFICATION());
  magnification->setRange(-100,100);
  isNormalize=new QCheckBox(tr("isNormalize"));
  isNormalize->setChecked(cv::SIFT::DescriptorParams::DEFAULT_IS_NORMALIZE);
  isNormalize->setChecked(false);
  recalculateAngles=new QCheckBox(tr("recalculateAngles"));
  recalculateAngles->setChecked(false);
  
  pb_load=new QPushButton(tr("Charger"),win);
  pb_save=new QPushButton(tr("Sauver"),win);
  common_params=new QLabel(tr("<b>Params globaux:</b>"));
  descr_params=new QLabel(tr("<b>Params descripteur:</b>"));
  detec_params=new QLabel(tr("<b>Params detecteur:</b>"));
  
  sift=NULL;
  
  glayout1->addWidget(sb_edgeThreshold,0,0);
  glayout1->addWidget(sb_threshold,0,1);
  glayout2->addWidget(magnification,0,0);
  glayout2->addWidget(isNormalize,0,1);
  glayout2->addWidget(recalculateAngles,0,2);
  
  glayout3->addWidget(use_mask,0,0);
  glayout3->addWidget(sb_nOctaves,1,0);
  glayout3->addWidget(sb_nOctaveLayers,1,1);
  glayout3->addWidget(sb_firstOctave,2,0);
  glayout3->addWidget(sb_angleMode,2,1);
  
  
  hlayout->addWidget(pb_load);
  hlayout->addWidget(pb_save);
  layout->addLayout(hlayout);
  layout->addWidget(common_params);
  layout->addWidget(mode);
  layout->addLayout(glayout3);
  layout->addWidget(detec_params);
  layout->addLayout(glayout1);
  layout->addWidget(descr_params);
  layout->addLayout(glayout2);
  
  connect(sb_edgeThreshold,SIGNAL(valueChanged(double)),this,SLOT(changeSIFTParams(double)));
  connect(sb_threshold,SIGNAL(valueChanged(double)),this,SLOT(changeSIFTParams(double)));
  connect(magnification,SIGNAL(valueChanged(double)),this,SLOT(changeSIFTParams(double)));
  connect(isNormalize,SIGNAL(	stateChanged(int)),this,SLOT(changeSIFTParams(int))); 
  connect(recalculateAngles,SIGNAL(stateChanged(int)),this,SLOT(changeSIFTParams(int)));
  
  connect(sb_nOctaves,SIGNAL(valueChanged(int)),this,SLOT(changeSIFTParams(int)));
  connect(sb_nOctaveLayers,SIGNAL(valueChanged(int)),this,SLOT(changeSIFTParams(int)));
  connect(sb_firstOctave,SIGNAL(valueChanged(int)),this,SLOT(changeSIFTParams(int)));
  connect(sb_angleMode,SIGNAL(valueChanged(int)),this,SLOT(changeSIFTParams(int)));
  
  connect(pb_load,SIGNAL(clicked()),this,SLOT(load()));
  connect(pb_save,SIGNAL(clicked()),this,SLOT(save()));
  
  connect(mode,SIGNAL(currentIndexChanged(int)),this,SLOT(setSIFTParams(int)));
  mode->setCurrentIndex(0);
  setSIFTParams(mode->currentIndex());
             
  siftTargets.clear();
}
////////////////////////////////////////////////////////////////////////////////
void ModuleSIFT::free()
{
  delete win;
}
////////////////////////////////////////////////////////////////////////////////
void ModuleSIFT::loadParams(QString filename)
{
  QFile file(filename);
  if(file.open(QIODevice::ReadOnly | QIODevice::Text))
  {
    QTextStream in(&file);
    
    int nd=0;
    double nf=0.0;
    
    //common parameters
    in>>nd;mode->setCurrentIndex(nd);
    in>>nd;use_mask->setChecked(nd);
    
    in>>nd;sb_nOctaves->setValue(nd);
    in>>nd;sb_nOctaveLayers->setValue(nd);
    in>>nd;sb_firstOctave->setValue(nd);
    in>>nd;sb_angleMode->setValue(nd);
    
    //detector parameters
    in>>nf;sb_threshold->setValue(nf);
    in>>nf;sb_edgeThreshold->setValue(nf);
    
    //descriptor parameters
    in>>nf;magnification->setValue(nf);
    in>>nd;isNormalize->setChecked(nd);
    in>>nd;recalculateAngles->setChecked(nd);
    
    file.close();
  }  
}
void ModuleSIFT::saveParams(QString filename)
{
  QFile file(filename);
  if(file.open(QIODevice::WriteOnly | QIODevice::Text))
  {
    QTextStream out(&file);
    
    //common parameters
    out<<mode->currentIndex()<<" "<<use_mask->isChecked()<<" ";
    
    out<<sb_nOctaves->value()<<" "<<sb_nOctaveLayers->value()<<" ";
    out<<sb_firstOctave->value()<<" "<<sb_angleMode->value()<<" ";
    
    //detector parameters
    out<<sb_threshold->value()<<" "<<sb_edgeThreshold->value()<<" ";
    
    //descriptor parameters
    out<<magnification->value()<<" "<<isNormalize->isChecked()<<" ";
    out<<recalculateAngles->isChecked();
    
    
    
    file.close();
  }
}
//////////////////////////////////////////////////////////////////////////////// 
cv::Mat & ModuleSIFT::process()
{
    if(!use_mask->isChecked()){mask.create(frame.rows,frame.cols,CV_8UC1);mask.setTo(255);}

		if(mode->currentIndex()==0)
    {
      siftTargets.clear();
			mask.setTo(0);
      (*sift)(frame, mask, siftTargets);
      
			keypoints.create(siftTargets.size(),7);
			for(unsigned int i=0;i<siftTargets.size();i++)
			{
				keypoints(i,0)=siftTargets[i].pt.x;
				keypoints(i,1)=siftTargets[i].pt.y;
				keypoints(i,2)=siftTargets[i].size;
				keypoints(i,3)=siftTargets[i].angle;
				keypoints(i,4)=siftTargets[i].response;
				keypoints(i,5)=siftTargets[i].octave;//octave			
				keypoints(i,6)=siftTargets[i].class_id;//label
			}
      //on retourne la liste des points sous forme de matrice
      return keypoints;
    }
    else if(mode->currentIndex()==1)
    { 
      (*sift)(frame, mask, siftTargets,descriptors,true);
      
      //on retourne la matrice des descripteurs sift
      return descriptors; 
    }
    else
    {
      siftTargets.clear();
      (*sift)(frame, mask, siftTargets,descriptors,false);
      
      //on retourne la matrice des descripteurs sift
      return descriptors;
    }
}
////////////////////////////////////////////////////////////////////////////////
void ModuleSIFT::setInput(int n,const cv::Mat & m)
{
  if(n==0)
  {
      frame.create(m.rows,m.cols,CV_8UC1);
      cvtColor(m, frame, CV_RGB2GRAY);
  }
  
  if(n==1 && use_mask->isChecked())
  {
    mask=m.clone();
  }
  else if(n==1 || n==2)
  {
    //la il faut convertir la matrice en vecteur de points clefs
    siftTargets.clear();
    for(int i=0;i<m.cols;i++)
    {
      cv::Mat_<float> keys=m; 
      cv::KeyPoint pt(keys(0,i),
                      keys(1,i),
                      keys(2,i),
                      keys(3,i),
                      keys(4,i),0,-1);
      siftTargets.push_back(pt);
    } 
  }
}
////////////////////////////////////////////////////////////////////////////////


QWidget* ModuleSIFT::widget()
{
  return win;
}
////////////////////////////////////////////////////////////////////////////////
QString ModuleSIFT::getName()
{
  return trUtf8("SIFT");
}
////////////////////////////////////////////////////////////////////////////////
int ModuleSIFT::nbInputs()
{
  return 1+use_mask->isChecked()+(mode->currentIndex()==1);
}
////////////////////////////////////////////////////////////////////////////////
bool ModuleSIFT::setParams(QString params)
{
	QStringList params_list=params.split(";");
	
	if(params_list.size()==11)
	{
		mode->setCurrentIndex(params_list[0].toInt());
		use_mask->setChecked(params_list[1].toInt());
		sb_nOctaves->setValue(params_list[2].toInt());
		sb_nOctaveLayers->setValue(params_list[3].toInt());
	  sb_firstOctave->setValue(params_list[4].toInt());
		sb_angleMode->setValue(params_list[5].toInt());
		sb_threshold->setValue(params_list[6].toFloat());
		sb_edgeThreshold->setValue(params_list[7].toFloat());
		magnification->setValue(params_list[8].toFloat());
		isNormalize->setChecked(params_list[9].toInt());
		recalculateAngles->setChecked(params_list[10].toInt());

		return true;
	}
	else
	{
		return false;
	}
}
////////////////////////////////////////////////////////////////////////////////
QString ModuleSIFT::getParams()
{
	return QString::number(mode->currentIndex())+tr(";")+
				 QString::number(use_mask->isChecked())+tr(";")+
				 QString::number(sb_nOctaves->value())+tr(";")+
				 QString::number(sb_nOctaveLayers->value())+tr(";")+
				 QString::number(sb_firstOctave->value())+tr(";")+
				 QString::number(sb_angleMode->value())+tr(";")+
				 QString::number(sb_threshold->value())+tr(";")+
				 QString::number(sb_edgeThreshold->value())+tr(";")+
				 QString::number(magnification->value())+tr(";")+
				 QString::number(isNormalize->isChecked())+tr(";")+
				 QString::number(recalculateAngles->isChecked());	
}

QCVModuleInterface * ModuleSIFT::clone()
{
	return new ModuleSIFT;
}

////////////////////////////////////////////////////////////////////////////////
QString ModuleSIFT::help(int id)
{
	if(id==-2)
	{
		 return tr("Module d'extration d'invarient d'echelle.");
	}	
	else if(id==-1)
	{
		if(mode->currentIndex()==0){return tr("keyspoints");}
		else {return tr("descripteurs");}
	}
	else if(id==0)
	{
		return tr("image");
	}
  else if(id==1)
	{
		if(mode->currentIndex()!=1){return tr("mask");}
		else {return tr("keyspoints");}
	}
	else if(id==2)
	{
		return tr("mask");
	}
	else 
	{
		return tr("erreur");
	}
}
////////////////////////////////////////////////////////////////////////////////
void ModuleSIFT::setSIFTParams(int type)
{                                           
  if(sift!=NULL)delete sift;
  
  if(type==0)//construction d'un detecteur
  {
    sift=new cv::SIFT(sb_threshold->value(),
                      sb_edgeThreshold->value(),
                      sb_nOctaves->value(),
                      sb_nOctaveLayers->value(),
                      sb_firstOctave->value(),
                      sb_angleMode->value()); 
                      
    sb_threshold->setEnabled(true);
    sb_edgeThreshold->setEnabled(true);
    recalculateAngles->setEnabled(false);
    isNormalize->setEnabled(false);
    magnification->setEnabled(false);
  }
  else if(type==1)//construction d'un descripteur
  {
    sift=new cv::SIFT(magnification->value(), 
                      isNormalize->isChecked(),
                      recalculateAngles->isChecked(),
                      sb_nOctaves->value(),
                      sb_nOctaveLayers->value(),
                      sb_firstOctave->value(),
                      sb_angleMode->value());
    
    sb_threshold->setEnabled(false);
    sb_edgeThreshold->setEnabled(false);
    recalculateAngles->setEnabled(true);
    isNormalize->setEnabled(true);
    magnification->setEnabled(true);
  }
  else if(type==2)//construction d'un descripteur
  {
    cv::SIFT::CommonParams commonParams;
    commonParams.nOctaves=sb_nOctaves->value();
    commonParams.nOctaveLayers=sb_nOctaveLayers->value();
    commonParams.firstOctave=sb_firstOctave->value();
    commonParams.angleMode=sb_angleMode->value();
    
    cv::SIFT::DetectorParams detectorParams;
    detectorParams.threshold=sb_threshold->value();
    detectorParams.edgeThreshold=sb_edgeThreshold->value();
    
    cv::SIFT::DescriptorParams descriptorParams;
    descriptorParams.magnification=magnification->value();
    descriptorParams.isNormalize=isNormalize->isChecked();
    descriptorParams.recalculateAngles=recalculateAngles->isChecked();
    
    sift=new cv::SIFT(commonParams,detectorParams,descriptorParams);
    
    
    sb_threshold->setEnabled(true);
    sb_edgeThreshold->setEnabled(true);
    recalculateAngles->setEnabled(true);
    isNormalize->setEnabled(true);
    magnification->setEnabled(true);
  }
}            
void ModuleSIFT::changeSIFTParams(double n)
{
  setSIFTParams(mode->currentIndex());
}
void ModuleSIFT::changeSIFTParams(int n)
{
  setSIFTParams(mode->currentIndex());
}
////////////////////////////////////////////////////////////////////////////////
void ModuleSIFT::load()
{
  QString fn=QFileDialog::getOpenFileName(NULL,"Ouvrir un fichier de parametres","params","*.cparam");
  if(!fn.isEmpty())
  {
    loadParams(fn);
  }
}
////////////////////////////////////////////////////////////////////////////////
void ModuleSIFT::save()
{
  QString fn=QFileDialog::getSaveFileName(NULL,"Sauver un fichier de parametres","params","*.cparam");
  if(!fn.isEmpty())
  {
    saveParams(fn);
  }
}
////////////////////////////////////////////////////////////////////////////////
Q_EXPORT_PLUGIN2(ModuleSIFT_plugin,ModuleSIFT);                                                                               
